home *** CD-ROM | disk | FTP | other *** search
/ MacFormat 1994 September / macformat-004.iso / Shareware City / Graphics / VideoToolbox ƒ / VideoToolboxSources / Timer.c < prev    next >
Encoding:
Text File  |  1994-07-07  |  10.4 KB  |  307 lines  |  [TEXT/KAHL]

  1. /*
  2. Timer.c
  3. An interval timer based on Apple's Time Manager. It returns the time that
  4. elapsed between calling StartTimer() and StopTimer(). Under System 7, which has
  5. the Extended Time Manager, the timing is very accurate (better than 1 part in
  6. 1000) and a precision of better than 200 µs. Under System 6 the precision will
  7. be about 1 ms, and the returned times will tend to be about 10% low due to
  8. documented deficiencies of Apple's old Revised Time Manager. The even older
  9. "Standard" Time Manager is not supported here, and will result in an error message.
  10.  
  11. One could easily add a PeekTimer() routine to this set, but I haven't
  12. gotten around to it. Send it to me if you write it.
  13.  
  14. Timer *NewTimer(void);
  15. void DisposeTimer(Timer *t);
  16. void StartTimer(Timer *t);
  17. long PeekTimer(Timer *t);                    // µs, up to 36 minutes
  18. long StopTimer(Timer *t);                    // µs, up to 36 minutes
  19. double StopTimerSecs(Timer *t);                // s, with µs precision and no time limit
  20. double PeekTimerSecs(Timer *t);                // s, with µs precision and no time limit
  21.  
  22.     Timer *timer;
  23.     long t;
  24.     
  25.     timer=NewTimer();
  26.     StartTimer(timer);
  27.     do(i=0;i<100;i++);
  28.     t=StopTimer(timer);
  29.     DisposeTimer(timer);
  30.     printf("One hundred iterations takes %ld µs\n",t);
  31.  
  32. The timing result comes in two flavors. StopTimer() returns the time in
  33. microseconds as a long, which can hold a time up to 2,147,483,647 µs, which is
  34. nearly 36 minutes. StopTimerSecs() returns the time in secs as a double, and can
  35. time an interval of essentially unlimited duration with microsecond precision.
  36. I generally prefer the latter, but if your program may run without a floating 
  37. point unit you might prefer the former.
  38.  
  39. You can have many Timers running at once. The only restriction is that you must
  40. create each Timer, by calling NewTimer() before you use it, and, obviously,
  41. should not use it after calling DisposeTimer().
  42.  
  43. The Time Manager seems to use interrupts at a higher rate than the 10 s interval
  44. I requested here (which would never expire in typical use). I infer this from
  45. the fact that disabling interrupts by SetPriority(7) greatly reduces the timing
  46. value returned by StopTimer(). So don't disable interrupts while you're timing.
  47.  
  48. StopTimer() adds a small offset (about 62 µs on a Mac IIci) to the raw time in
  49. order to return an unbiased estimate of the time from when StartTime was called
  50. to when StopTime() was called. This is the most useful time measure for
  51. measuring the interval between two events (e.g. video frames). Alternatively, if
  52. you wish to measure processing time, then it is more useful to compute the time
  53. from when StartTimer() returns to when StopTimer() is called. For this you
  54. simply subtract the fixed duration of StartTimer(), call-to-return, which is
  55. provided in µs in your Timer structure in the long structure member
  56. "timeToStartTimer".
  57.  
  58.     betweenTime = StopTimer(t) - t->timeToStartTimer;
  59.     
  60. If you use StopTimerSecs() instead you'll have to divide timeToStartTimer by a
  61. million to convert µs to s. For some purposes this offset is negligibly
  62. small. On my Mac IIci the timeToStartTimer is 240 µs. This offset, and the one
  63. mentioned above are measured automatically the first time you call NewTimer().
  64. Alternatively, instead of having to remember the name of the structure member,
  65. just measure the timeToStartTimer yourself and subtract it from all subsequent
  66. measures:
  67.  
  68.     StartTimer(t);
  69.     s0=StopTimerSec(t);
  70.  
  71. The Timer structures are kept in a linked list so that KillEveryTimer(), which
  72. is placed in the _atexit() queue, can find and kill them when the application
  73. exits.
  74.  
  75. HISTORY:
  76. 8/19/92 dgp    based on Time Manager chapter in Inside Mac VI and TimeIt.c, 
  77.     which is now obsolete. I also benefited from examining code by Jonothan Kolodny
  78.     forwarded to me by Thomas Busey.
  79. 8/27/92    dgp    Rewrote everything. Made the interrupt service routine reentrant 
  80.     by eliminating all use of global variables, using only the structure pointed
  81.     to by A1. There can now be an unlimited number of timers active at once.
  82.     Added NewTimer() and DisposeTimer() to manage them. 
  83. 9/10/92    dgp    added calls to VM to HoldMemory() and UnHoldMemory(). According to Apple's
  84.     Memory book this isn't strictly necessary, since Time Manager tasks will be
  85.     called only when it's safe.
  86. 1/11/93 dgp StopTimerSecs() now returns NAN if called with a NULL pointer.
  87. 7/9/93    dgp    Test MATLAB in if() instead of #if. 
  88. 5/28/94 dgp Made compatible with Apple's Universal Headers. Thanks to Bob Dougherty 
  89. (wolfgang@cats.ucsc.edu) for reporting the incompatibility.
  90. */
  91. #include "VideoToolbox.h"
  92. static pascal void TimerTask(void);
  93. void KillEveryTimer(void);
  94. Ptr GetA1(void);
  95.  
  96. /* This is a copy for reference. Original is in VideoToolbox.h.
  97. struct Timer{
  98.     TMTask time;
  99.     long ourA5;
  100.     long interval,elapsed,elapsedIntervals;
  101.     long timeToStartTimer;            // minimum time in µs
  102.     long stopDelay;                    // µs from call to stop, re from call to start
  103.     long timeManagerVersion;
  104.     struct Timer *next,*previous;    // doubly linked list of Timers
  105. };
  106. */
  107.  
  108. static Timer defaultTimer,qTimer={0,0,0,0,0,0,0,0,0,0,0};
  109. static long vmPresent=0;
  110. #define TASK_SIZE 1000    // Generous guess for size of routine
  111.  
  112. Timer *NewTimer(void)
  113. {
  114.     static short firstTime=1;
  115.     extern Timer defaultTimer,qTimer;
  116.     Timer *t,*tt;
  117.     long j;
  118.     
  119.     if(firstTime){
  120.         firstTime=0;
  121.         qTimer.next=qTimer.previous=NULL;
  122.         if(!MATLAB)_atexit(KillEveryTimer);
  123.         Gestalt(gestaltVMAttr,&vmPresent);
  124.         vmPresent &= gestaltVMPresent;
  125.         t=&defaultTimer;
  126.         t->ourA5 = SetCurrentA5();
  127.         #if defined(NewTimerProc)
  128.             t->time.tmAddr = NewTimerProc(TimerTask);
  129.         #else
  130.             t->time.tmAddr = (TimerProcPtr)TimerTask;
  131.         #endif
  132.         t->time.tmCount=t->time.tmWakeUp=t->time.tmReserved=0;
  133.         t->elapsedIntervals=t->elapsed=0;                            
  134.         t->timeManagerVersion=0;
  135.         Gestalt(gestaltTimeMgrVersion,&t->timeManagerVersion);
  136.         switch(t->timeManagerVersion){
  137.         case 0:
  138.         case gestaltStandardTimeMgr:
  139.             printf("NewTimer: old System lacks the Revised Time Manager.\n");
  140.             return NULL;
  141.         case gestaltRevisedTimeMgr:
  142.             t->interval = 1L;                // Set the timer interval to 1 ms
  143.             break;
  144.         case gestaltExtendedTimeMgr:
  145.         default:
  146.             t->interval = -10000000L;        // Set the timer interval to 10 s
  147.         }
  148.         t->next=NULL;
  149.         t->previous=&qTimer;
  150.         t->timeToStartTimer=t->stopDelay=0;
  151.         
  152.         // Measure timeToStartTimer and stopDelay offsets.
  153.         t=NewTimer();
  154.         StartTimer(t);
  155.         t->timeToStartTimer=StopTimer(t);
  156.         tt=NewTimer();
  157.         StartTimer(t);
  158.         StartTimer(tt);
  159.         j=StopTimer(t);
  160.         defaultTimer.stopDelay=t->stopDelay=2*t->timeToStartTimer-j;
  161.         // The computed "cycle" interval will have stopDelay removed.
  162.         // The user wishing to compute the "between" interval will be
  163.         // subtracting the timeToStartTimer, so we should subtract 
  164.         // the stopDelay from that
  165.         // so the stopDelay cancels out when "between" time is computed.
  166.         defaultTimer.timeToStartTimer=t->timeToStartTimer-=t->stopDelay;
  167.         DisposeTimer(tt);
  168.         return t;
  169.     }
  170.     t=(Timer *)NewPtr(sizeof(Timer));
  171.     if(t!=NULL){
  172.         *t=defaultTimer;
  173.         t->next=qTimer.next;
  174.         if(t->next!=NULL)t->next->previous=t;
  175.         qTimer.next=t;
  176.         if(vmPresent){
  177.             HoldMemory(t,sizeof(*t));
  178.             HoldMemory(t->time.tmAddr,TASK_SIZE);
  179.         }
  180.         if(t->timeManagerVersion==gestaltRevisedTimeMgr)InsTime((QElemPtr)t);
  181.         else InsXTime((QElemPtr)t);
  182.     }
  183.     return t;
  184. }
  185.  
  186. void DisposeTimer(Timer *t)
  187. {
  188.     Timer *tt;
  189.     
  190.     if(t==NULL)return;
  191.     RmvTime((QElemPtr)t);
  192.     if(vmPresent){
  193.         UnholdMemory(t,sizeof(*t));
  194.         UnholdMemory(t->time.tmAddr,TASK_SIZE);
  195.     }
  196.     t->previous->next=t->next;
  197.     if(t->next!=NULL)t->next->previous=t->previous;
  198.     DisposPtr((Ptr)t);
  199. }
  200.  
  201. void StartTimer(Timer *t)
  202. {
  203.     if(t==NULL)return;
  204.     PrimeTime((QElemPtr)t,t->interval);
  205. }
  206.  
  207. long PeekTimer(Timer *t)                            // Returns µs
  208. {
  209.     long microSeconds;
  210.     
  211.     microSeconds=StopTimer(t);
  212.     StartTimer(t);
  213.     if(t->interval>0){
  214.         t->elapsed+=microSeconds/1000;
  215.         t->elapsedIntervals+=microSeconds/(t->interval*1000);
  216.     }else{
  217.         t->elapsed-=microSeconds;
  218.         t->elapsedIntervals-=microSeconds/t->interval;
  219.     }
  220.     return microSeconds;
  221. }
  222.  
  223. long StopTimer(Timer *t)                            // Returns µs
  224. {
  225.     register long microSeconds;
  226.     extern Timer defaultTimer;
  227.  
  228.     if(t==NULL)return 0;
  229.     RmvTime((QElemPtr)t);
  230.     // add up the elapsed intervals plus the one we're in, minus the time left
  231.     microSeconds=t->elapsed + t->interval;
  232.     if(microSeconds>0) microSeconds*=1000;            // convert ms to µs
  233.     else microSeconds=-microSeconds;
  234.     if(t->time.tmCount>0)t->time.tmCount*=-1000;    // convert ms to -µs
  235.     microSeconds+=t->time.tmCount;                    // -µs until end of interval
  236.     microSeconds-=t->stopDelay;                        // compute "cycle" time
  237.     
  238.     // Reinstall the Timer, to be ready for for another call to StartTimer()
  239.     t->time.tmWakeUp=t->time.tmReserved=t->elapsed=t->elapsedIntervals=0;                            
  240.     if(t->timeManagerVersion==gestaltRevisedTimeMgr)InsTime((QElemPtr)t);
  241.     else InsXTime((QElemPtr)t);
  242.     
  243.     return microSeconds;
  244. }
  245.  
  246. double StopTimerSecs(Timer *t)                        // Returns secs
  247. {
  248.     double s;
  249.     extern Timer defaultTimer;
  250.  
  251.     if(t==NULL)return NAN;
  252.     RmvTime((QElemPtr)t);
  253.     // add up the elapsed intervals plus the one we're in, minus the time left
  254.     t->elapsedIntervals++;
  255.     if(t->interval>0) s=t->elapsedIntervals*1000.0*t->interval;        // ms
  256.     else s=(double)-t->elapsedIntervals*t->interval;                // µs
  257.     if(t->time.tmCount>0) s-=t->time.tmCount*1000.0;    // -ms until end of interval
  258.     else s+=t->time.tmCount;                            // -µs until end of interval
  259.     s-=t->stopDelay;                                    // compute "cycle" time
  260.     s*=0.000001;
  261.     
  262.     // Reinstall the Timer, to be ready for for another call to StartTimer()
  263.     t->time.tmWakeUp=t->time.tmReserved=t->elapsed=t->elapsedIntervals=0;                            
  264.     if(t->timeManagerVersion==gestaltRevisedTimeMgr)InsTime((QElemPtr)t);
  265.     else InsXTime((QElemPtr)t);
  266.     
  267.     return s;
  268. }
  269.  
  270. // KillEveryTimer turns off all our Timers before we quit. There is no need to
  271. // free the space since the system will do that automatically.
  272.  
  273. void KillEveryTimer(void)
  274. {
  275.     Timer *t;
  276.     extern Timer qTimer;
  277.  
  278.     t=qTimer.next;
  279.     while(t!=NULL){
  280.         RmvTime((QElemPtr)t);
  281.         if(vmPresent){
  282.             UnholdMemory(t,sizeof(*t));
  283.             UnholdMemory(t->time.tmAddr,TASK_SIZE);
  284.         }
  285.         t=t->next;
  286.     }
  287. }
  288.  
  289. // The Revised & Extended Time managers set A1=&task.time before calling TimerTask
  290. // The code allowing access to globals is commented out because it is not needed here.
  291.  
  292. #pragma options(!profile)    // it would be dangerous to call the profiler from here
  293. Ptr GetA1(void)=0x2009;        // MOVE.L A1,D0
  294.  
  295. static pascal void TimerTask(void)                // Called at interrupt time
  296. {
  297.     long oldA5;
  298.     Timer *t;
  299.  
  300.     t=(Timer *)GetA1();
  301. //    oldA5 = SetA5(t->ourA5);                    // Reestablish A5 for global variables
  302.     PrimeTime((QElemPtr)t,t->interval);            // Repeat the interval
  303.     t->elapsed += t->interval;                    // Increment the time count
  304.     t->elapsedIntervals++;
  305. //    SetA5(oldA5);                                 // Restore A5
  306. }
  307.